package com.marver.common.handler;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.marver.common.Audience;
import com.marver.common.Global;
import com.marver.common.annotation.AccessNum;
import com.marver.common.annotation.TokenFilter;
import com.marver.common.enumeration.IpTypEnum;
import com.marver.common.filter.MyRequestWrapper;
import com.marver.common.filter.MyResponseWrapper;
import com.marver.common.result.Result;
import com.marver.common.result.ResultStatus;
import com.marver.common.utils.*;
import com.marver.entity.Log;
import com.marver.entity.qo.Param;
import com.marver.login.service.LogService;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;

/**
 * @description: 访问拦截器
 * @Author: Marver
 * @date: 2018-04-19 16:55
 **/
public class AccessRestrictiHandler implements HandlerInterceptor {
    @Autowired
    private Audience audienceEntity;
    @Autowired
    private LogService logService;
    private UrlPathHelper urlPathHelper = new UrlPathHelper();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //过滤options请求
        if("OPTIONS".equals(request.getMethod())){
            return true;
        }
        //备份HttpServletRequest。解決ServletInputStream重复读取问题
        MyRequestWrapper requestWrapper = new MyRequestWrapper(request);
        //從HttpServletResponse讀取返回的body內容
        MyResponseWrapper responseWrapper = new MyResponseWrapper(response);

        String requestParam="";
        String userNum = "";
        //判断是否为ajax请求，默认不是
        boolean isAjaxRequest = false;
        if(StringUtils.isNotBlank(request.getHeader("x-requested-with")) && request.getHeader("x-requested-with").equals("XMLHttpRequest")){
            isAjaxRequest = true;
        }

        ObjectMapper mapper = new ObjectMapper();
        String requestURL = urlPathHelper.getLookupPathForRequest(requestWrapper);
        requestURL = requestURL.replace("/","");

        //獲取Body傳過來的參數（ajax data提交上来的参数）-----------開始
        StringBuffer jb = new StringBuffer();
        String line = null;
        BufferedReader reader = requestWrapper.getReader();
        while ((line = reader.readLine()) != null){
            jb.append(line);
        }
        requestParam = jb.toString();
        //獲取Body傳過來的參數（ajax data提交上来的参数）-----------結束

        //獲取url參數-------開始
        Enumeration enu=request.getParameterNames();
        String apiAdderss=requestWrapper.getRequestURL()+"?";
        while(enu.hasMoreElements()){
            String paraName=(String)enu.nextElement();
            apiAdderss=apiAdderss+paraName+"="+request.getParameter(paraName)+"&";
        }
        apiAdderss=apiAdderss.substring(0, apiAdderss.length()-1);
        String requestIp = HttpUtil.getClientIP(request);

        /**
         * 1.查询此IP此次访问url是否存在redis中
         * 2.存在则返回请求限制信息
         * 3.不存在则从数据库日志表查询最近一分钟数据，有数据则返回限制信息
         * 4.无数据，则加入redis缓存中 此次请求通过
         */
        if(handler instanceof HandlerMethod) {
            AccessNum accessNum = ((HandlerMethod) handler).getMethodAnnotation(AccessNum.class);
            Boolean accessFlag = setupAccessLimit(response, requestParam, mapper, requestWrapper, requestURL, apiAdderss, accessNum);
            if (!accessFlag) return false;

            //token过滤
            TokenFilter tokenFilter = ((HandlerMethod) handler).getMethodAnnotation(TokenFilter.class);
            if(tokenFilter!=null){
                if(tokenFilter.isFilter()){
                    byte[] responseBody;
                    //寫日誌
                    Log log = logService.addLog(requestIp, userNum, apiAdderss, requestParam);
                    responseBody = responseWrapper.getResponseData();
                    //更新日誌的輸出
                    logService.updateLog(log.getId(), new String(responseBody));
                    return true;
                }
            }
        }

        Param param = JSONObject.parseObject(requestParam,Param.class);
        String token = param.getHeader().getAccessToken();
        /**
         * 限制黑白名单访问
         */
        if (token != null) {
            Claims claims = JwtHelper.parseJWT(token, audienceEntity.getBase64Secret());
            if(claims!=null){
                String appId = claims.get("appid").toString();
                String blackIp  = JedisUtils.get(IpTypEnum.BLACK_IP.getKey()+appId);
                String whiteIp = JedisUtils.get(IpTypEnum.WHITE_IP.getKey()+appId);
                //包含在黑名单中则不返回信息
                if(StringUtils.isNotEmpty(blackIp)){
                    if(CheckIpUtils.checkAccessIP(requestIp,blackIp)){
                        return false;
                    }
                }
                //不包含在白名单中则不返回信息
                if(StringUtils.isNotEmpty(whiteIp)){
                    if(!CheckIpUtils.checkAccessIP(requestIp,whiteIp)){
                        return false;
                    }
                }
                return accessToken(requestIp, response, claims, responseWrapper, requestParam, isAjaxRequest, mapper, apiAdderss);
            }
        }
        Result resultMsg;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");

        resultMsg = new Result(ResultStatus.invalid_token.getErrorCode());
        response.getOutputStream().write(mapper.writeValueAsBytes(resultMsg));
        logService.addLog(requestIp, userNum, apiAdderss, requestParam,mapper.writeValueAsString(resultMsg));
        return false;
    }

    /**
     * token验证
     * @param requestIp
     * @param response
     * @param claims
     * @param responseWrapper
     * @param requestParam
     * @param isAjaxRequest
     * @param mapper
     * @param apiAdderss
     * @return
     * @throws IOException
     */
    private boolean accessToken(String requestIp, HttpServletResponse response, Claims claims, MyResponseWrapper responseWrapper, String requestParam, boolean isAjaxRequest, ObjectMapper mapper, String apiAdderss) throws IOException {
        byte[] responseBody;
        Result resultMsg;
        //判断获取Token的IP与现在请求的IP是否一致，如果不一致则报安全隐患
        if(claims.containsKey("ip")&&
                claims.get("ip").toString().equals(requestIp)){
            if(isAjaxRequest){
                //寫日誌
                Log log=logService.addLog(requestIp, claims.getIssuer(), apiAdderss, requestParam);
                responseBody=responseWrapper.getResponseData();
                //更新日誌的輸出
                logService.updateLog(log.getId(), new String(responseBody));
                response.getOutputStream().write(responseBody);
            }
        }else{
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            resultMsg = new Result(ResultStatus.FAIL.getErrorCodeToString(),"IP发生变化,重新获取token");
            response.getOutputStream().write(mapper.writeValueAsBytes(resultMsg));
            return false;
        }
        return true;
    }

    /**
     * 设置访问限制
     * @param response
     * @param requestParam
     * @param mapper
     * @param requestWrapper
     * @param requestURL
     * @param apiAdderss
     * @param accessNum
     * @return
     * @throws IOException
     */
    private Boolean setupAccessLimit(HttpServletResponse response, String requestParam, ObjectMapper mapper, MyRequestWrapper requestWrapper, String requestURL, String apiAdderss, AccessNum accessNum) throws IOException {
        Result resultMsg;
        if(accessNum != null){
            String userIp = HttpUtil.getIpAddr(requestWrapper).replace(".","");
            //分钟限制key
            String minKey = Global.ACCESSLIMIT+userIp+":"+requestURL+requestParam.hashCode();
            String minNum = JedisUtils.get(minKey);
            if(StringUtils.isNotEmpty(minNum)){
                int minCount = Integer.valueOf(minNum);
                if(Integer.valueOf(minNum) >=accessNum.minVisitsNum() && accessNum.minVisitsNum()!=0){
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    resultMsg = new Result(ResultStatus.overclocking.getErrorCode());
                    response.getOutputStream().write(mapper.writeValueAsBytes(resultMsg));
                    return false;
                }else {
                    minCount+=1;
                    JedisUtils.set(minKey,String.valueOf(minCount),60);
                }
            }else {
                int num = logService.getLogSum(apiAdderss, requestParam, HttpUtil.getIpAddr(requestWrapper));
                if(num>=accessNum.minVisitsNum() && accessNum.minVisitsNum()!=0){
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    resultMsg = new Result(ResultStatus.overclocking.getErrorCode());
                    response.getOutputStream().write(mapper.writeValueAsBytes(resultMsg));
                    JedisUtils.set(minKey,"1",60);
                    return false;
                }
            }
            //一天的访问次数 判断是否需要限制
            String day = DateUtils.date2String(new Date(),"yyyy-MM-dd");
            String dayKey = Global.ACCESSLIMIT+userIp+":"+requestURL+day;
            int count = 0;
            String num = JedisUtils.get(dayKey);
            //redis不存在则为第一次访问 否则进行+1
            if (StringUtils.isEmpty(num)) {
                count = 1;
            }else{
                count = Integer.valueOf(num)+1;
            }
            JedisUtils.set(dayKey,String.valueOf(count),24 * 60 * 60);
            //访问次数超出访问限制次数 返回错误
            if(count>accessNum.dayVisitsNum() && accessNum.dayVisitsNum()!=0){
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json; charset=utf-8");
                resultMsg = new Result(ResultStatus.transfinite.getErrorCode());
                response.getOutputStream().write(mapper.writeValueAsBytes(resultMsg));
                return false;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}
